Ícone Ver README do projeto

Introdução

O KNN é um algoritmo de classificação bem simples. Ele memoriza um conjunto de dados de treinamento e prevê “a classe de um novo ponto de dados com base na classe dos na classe dos vizinhos mais próximos deste ponto, assumindo que pontos próximos tem uma maior chance de pertencer à mesma categoria” (FONTANA, 2020).

Isso significa que ao contrário de muitos algoritmos de classificação, o KNN não faz nenhum aprendizado. Ele simplesmente armazena os dados de treinamento literalmente.

Quando um novo dado de teste é apresentado, o k-NN compara sua proximidade com os dados de treinamento. E então, ele seleciona os k vizinhos mais próximos e atribui um rótulo a este novo ponto.

Para fazer esta previsão nos dados de teste, ele calcula a distância entre dois pontos. Existem várias distâncias que podem ser utilizadas pelo KNN, como a Euclidiana e a Manhatan.

Este algoritmo não requer ajuste de parâmetro durante o treinamento, porém é muito importante escolher com sabedoria o valor do k. Um valor de k muito pequeno, permite com que dados ruidosos ou exceções influenciem na rotulagem. Porém escolher um k muito grande, faz com que o algoritmo ignore pequenos padrões importantes. Este problema é conhecido como compensação de viés-variância (LANTZ, 2019).

Na figura abaixo, queremos descobrir a categoria do ponto cinza . Se escolhermos k =1, o algoritmo vai classifica-lo como verde. Porém, se usarmos um k=3 , teremos uma classificação mais adequada, pois o ponto será rotulado como laranja.

Exemplo

No KNN um novo ponto é classificado de acordo com a moda dos “K” vizinhos mais próximos.
No exemplo a seguir um usuário assistiu o filme “x” (novo ponto de dado). E, nós queremos descobrir se vamos recomendar o filme “A” ou “B”. As variáveis independentes são número de avalições recebidas por filme e avaliação do usuário, numa escala de 0 à 5.


Na fig(1), estipulamos que “K” é igual a 3. Então vamos considerar os pontos mais próximos de “x”. Como resultado o filme “A” tem uma moda maior. Portanto, a recomendação para quem assistiu o filme “x” é assistir o filme “A”.

Já na fig(2), o “K” é igual a 5. Isto faz com que a o filme “B” tenha uma moda maior. E a recomendação será diferente. Neste caso, a recomendação para quem assistiu o filme “x” é assistir o filme “B”.

Para encontrar os “K” vizinhos mais próximos é necessário medir a distância entre os pontos. Existem várias maneiras de fazer, e uma dela é utilizando a distância euclidiana. Ela funciona da seguinte maneira:

  • Primeiro traçamos um triângulo retângulo e calculamos a variação entre os dois pontos das n-dimensões.
  • Em seguida, elevamos estas variações ao quadrado e somamos elas.
  • Calculamos a raiz quadrada desta soma.

No final teremos a distância euclidiana que nada mais é que a hipotenusa do triângulo retângulo traçado.
No exemplo a seguir, calculamos a distância euclidiana dos pontos que correspondem ao filme “A” e o filme “x”. Temos duas dimensões, pois são duas variáveis independentes.


É possível calcular a distância euclidiana quando há mais de duas dimensões, ou seja, mais de duas variáveis independentes. A fórmula é:



Um processo similiar ao descrito no exemplo será feito nesta etapa.

Escolha do K

Embora não haja uma fórmula ou atalho específico para determinar o valor de K , existem certas abordagens que sugerem os melhores valores possíveis de K . Uma dessas abordagens é definir K como a raiz quadrada do número de itens no conjunto de dados de treinamento (LANTZ, 2019).

Neste caso, o número de itens é 24.326 (filmes) e a raiz quadrada é aproximadamente 155.

np.sqrt(24326)
np.float64(155.96794542469294)
%load_ext pretty_jupyter

Importar bibliotecas

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from sklearn.neighbors import NearestNeighbors
from sklearn.metrics import confusion_matrix

# KNeighborsClassifier é um modelo de classificação
#  baseado no algoritmo K-Nearest Neighbors (KNN) da biblioteca scikit-learn.
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor

# confusion_matrix, accuracy_score, precision_score, 
#   recall_score, f1_score: Funções do scikit-learn 
#   para calcular métricas de desempenho.
from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import BaggingClassifier
import time
from tqdm import tqdm
from sklearn.base import clone
# Usado no Modelo 2
from scipy.sparse import csr_matrix
from sklearn.neighbors import NearestNeighbors
from sklearn.metrics import pairwise_distances
from sklearn.metrics.pairwise import pairwise_kernels
import joblib

Carregar arquivos


Tabelas para modelagem 1: filmes

# Dados de treino
knn_filmes_treino = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.3_Datasets_Transformação_parte_3/knn_filmes_treino.pickle", compression='gzip')
# Dados de teste
knn_filmes_teste = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.3_Datasets_Transformação_parte_3/knn_filmes_teste.pickle", compression='gzip')
# Olhar tabela de treino para modelagem 1: Filmes
knn_filmes_treino
title (2019) "Great Performances" Cats (1998) #Alive (2020) #Female Pleasure (2018) #Iamhere (2020) #UNFIT: The Psychology of Donald Trump (2019) $ (Dollars) (1971) $5 a Day (2008) $9.99 (2008) $ellebrity (Sellebrity) (2012) ... Üvegtigris (2001) Τέλειοι Ξένοι (2016) Χούλιγκανς: Κάτω τα χέρια απ' τα νιάτα! (1983) Делай - раз! (1989) Каменная башка (2008) Карусель (1970) Он вам не Димон (2017) Пес Барбос и необычный кросс (1961) Я худею (2018) …And the Fifth Horseman Is Fear (1965)
userId
5 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
15 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
49 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
119 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
134 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
330651 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330661 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330811 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330949 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330963 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

7943 rows × 24326 columns

# Olhar tabela de teste para modelagem 1: Filmes
knn_filmes_teste
title #Alive (2020) $ (Dollars) (1971) '71 (2014) '83 (2021) 'Hellboy': The Seeds of Creation (2004) 'Round Midnight (1986) 'Salem's Lot (2004) 'Til There Was You (1997) 'burbs, The (1989) 'night Mother (1986) ... tick, tick...BOOM! (2021) xXx (2002) xXx: Return of Xander Cage (2017) xXx: State of the Union (2005) ¡Three Amigos! (1986) ¿Quién mató a Bambi? (2013) À nous la liberté (Freedom for Us) (1931) Ánimas (2018) Épouse-moi mon pote (2017) Ужас, который всегда с тобой (2007)
userId
128 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
172 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
465 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
598 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
919 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
330236 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330321 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330496 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330667 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330948 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

1986 rows × 15496 columns

Modelo 1

Para rodar o modelo 1, usamos KNeighborsClassifier() e KNeighborsRegressor(). No KNeighborsClassifier() , criou-se um ponto de corte, em que avaliações menores que 3,5 era trocadas por 0 (usuário não gostou do filme) e avaliações acima de 3,5 eram trocadas por 1 (usuário gostou do filme).

Entretanto, o modelo demorou muito para ser rodado e o resultado não foi satisfatório. Por isso, focamos no KNeighborsRegressor( ) e geramos o ponto de corte após rodar o modelo.

Preparar os Dados

Ponto de Corte

# Definir um ponto de corte
#corte = 3.5  # ponto de corte

# Transformar em rótulos binários
#knn_filmes_treino1 = knn_filmes_treino.applymap(lambda x: 1 if x > corte else 0)
#knn_filmes_treino1
# Definir um ponto de corte
#corte = 3.5  # ponto de corte

# Transformar em rótulos binários
#knn_filmes_teste1 = knn_filmes_teste.applymap(lambda x: 1 if x > corte else 0)

Dados de treino: Separar Variáveis de Entrada (X) e Saída (y)

Vamos utilizar reshape(-1,1)para mudar a forma de matriz. Vamos criar um array com 1 coluna e quantas linhas forem necessárias, resultando em um array 2D no formato (n,1).
Fonte: Understanding the Differences Between Numpy Reshape(-1, 1) and Reshape(1, -1)

# Usando KNeighborRegressor() ----------
# X_train são os índices dos usuários no DataFrame train convertidos para um array 2D.
X_train_filmes = knn_filmes_treino.index.values.reshape(-1, 1)
#y_train são os valores dos filmes que os usuários assistiram no DataFrame train
y_train_filmes = knn_filmes_treino.values

# Usando KNeighborsClassifier() ----------
# X_train são os índices dos usuários no DataFrame train convertidos para um array 2D.
#X_train_filmes1 = knn_filmes_treino1.index.values.reshape(-1, 1)
# y_train são os valores dos filmes que os usuários assistiram no DataFrame train
#y_train_filmes1 = knn_filmes_treino1.values
X_train_filmes
array([[     5],
       [    15],
       [    49],
       ...,
       [330811],
       [330949],
       [330963]])
y_train_filmes 
array([[0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       ...,
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.]])

Dados de Teste: Separar Variáveis de Entrada (X) e Saída (y)

# Usando KNeighborRegressor() ----------
# Separar variáveis de entrada (X) e saída (y) para o teste
X_test_filmes = knn_filmes_teste.index.values.reshape(-1, 1)
y_test_filmes = knn_filmes_teste.values

# Usando KNeighborsClassifier() ----------
# Separar variáveis de entrada (X) e saída (y) para o teste
#X_test_filmes1 = knn_filmes_teste1.index.values.reshape(-1, 1)
#y_test_filmes1 = knn_filmes_teste1.values
X_test_filmes
array([[   128],
       [   172],
       [   465],
       ...,
       [330496],
       [330667],
       [330948]])
y_test_filmes
array([[0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       ...,
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.]])


Treinar o Modelo 1

Testamos diferentes valores de n_neighbors usando KNeighborsRegressor e KNeighborsClassifier.

# Usando KNeighborRegressor() ----------
# Treinar o modelo KNN com os dados de treinamento (X_train e y_train)
# Usando KNeighborsRegressor()
modelo_filmes = KNeighborsRegressor(n_neighbors=155)
modelo_filmes.fit(X_train_filmes, y_train_filmes)


# Usando KNeighborsClassifier()-------
#modelo_filmes1 = KNeighborsClassifier(n_neighbors=47)
#modelo_filmes1.fit(X_train_filmes1, y_train_filmes1)
KNeighborsRegressor(n_neighbors=155)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

Como o resultado usando KNeighborsClassifier() demorou muito para terminar, vamos salvá-los.

# SALVAR O MODELO
# Usando KNeighborsClassifier()-------

# Salvar modelo treinado (k=9)
#joblib.dump(modelo_filmes1,'C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/5.Modelagem_KNN/KNN_Classificacao_k_9/modelo_filmes1_Classificacao_k_9.joblib')

# Salvar modelo treinado (k=47)
#joblib.dump(modelo_filmes1,'C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/5.Modelagem_KNN/KNN_Classificacao_k_47/modelo_filmes1_Classificacao_k_47.joblib')
['C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/5.Modelagem_KNN/KNN_Classificacao_k_47/modelo_filmes1_Classificacao_k_47.joblib']

Prever Recomendações para o Conjunto de Teste

Vamos fazer as previsões para os dados de teste (X_test_filmes) usando o modelo KNN treinado.

# Usando KNeighborRegressor() ----------
# Usa o modelo treinado para fazer previsões (y_pred) para os dados de teste (X_test).
y_pred_filmes = modelo_filmes.predict(X_test_filmes)

# Usando KNeighborsClassifier()-------
# Usa o modelo treinado para fazer previsões (y_pred) para os dados de teste (X_test).
#y_pred_filmes1 = modelo_filmes1.predict(X_test_filmes1)
# SALVAR A TABELA
# Usando KNeighborsClassifier()-------

# Salvar previsão do modelo (k=9)
#joblib.dump(y_pred_filmes1, 'C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/5.Modelagem_KNN/KNN_Classificacao_k_9/ y_pred_filmes1_Classificacao_k_9.joblib')

# Salvar previsão do modelo (k=47)
#joblib.dump(y_pred_filmes1, 'C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/5.Modelagem_KNN/KNN_Classificacao_k_47/ y_pred_filmes1_Classificacao_k_47.joblib')
['C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/5.Modelagem_KNN/KNN_Classificacao_k_47/ y_pred_filmes1_Classificacao_k_47.joblib']
y_pred_filmes 
array([[0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       ...,
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.]])

Converter Previsões em um DataFrame

Para facilitar a visualização das recomendações, vamos transformar previsões em um dataframe em que 0 siginifica não recomendado.

# Verificar as dimensões (linhas, colunas)
knn_filmes_treino.shape
(7943, 24326)
# Verificar as dimensões (linhas, colunas)
y_pred_filmes.shape
(1986, 24326)
# Verificar as dimensões (linhas, colunas)
knn_filmes_teste.shape
(1986, 15496)

Previsões em um DataFrame

# Converte as previsões (y_pred) em um DataFrame (pred_df)
#  com as mesmas colunas do DataFrame train e o índice do DataFrame test.
#
# Usando KNeighborRegressor() ----------
# Exibe as recomendações de filmes para os usuários no conjunto de teste
df_pred_filmes = pd.DataFrame(y_pred_filmes, columns=knn_filmes_treino.columns, index=knn_filmes_teste.index)
print("Recomendações de filmes para o conjunto de teste:")
df_pred_filmes 

# Usando KNeighborsClassifier()-------
#df_pred_filmes1 = pd.DataFrame(y_pred_filmes1, columns=knn_filmes_treino1.columns, index=knn_filmes_teste1.index)
#print("Recomendações de filmes para o conjunto de teste:")
#df_pred_filmes 
Recomendações de filmes para o conjunto de teste:
title (2019) "Great Performances" Cats (1998) #Alive (2020) #Female Pleasure (2018) #Iamhere (2020) #UNFIT: The Psychology of Donald Trump (2019) $ (Dollars) (1971) $5 a Day (2008) $9.99 (2008) $ellebrity (Sellebrity) (2012) ... Üvegtigris (2001) Τέλειοι Ξένοι (2016) Χούλιγκανς: Κάτω τα χέρια απ' τα νιάτα! (1983) Делай - раз! (1989) Каменная башка (2008) Карусель (1970) Он вам не Димон (2017) Пес Барбос и необычный кросс (1961) Я худею (2018) …And the Fifth Horseman Is Fear (1965)
userId
128 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
172 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
465 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
598 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
919 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
330236 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330321 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330496 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330667 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
330948 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

1986 rows × 24326 columns

# SALVAR A TABELA
# Usando KNeighborsClassifier()-------

# Salvar as previsões convertidas em um DataFrame (k=9)
#df_pred_filmes1.to_pickle('C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/5.Modelagem_KNN/KNN_Classificacao_k_9/ df_pred_filmes1_Classificacao_k_9.pickle', compression='gzip')

# Salvar as previsões convertidas em um DataFrame (k=47)
#df_pred_filmes1.to_pickle('C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/5.Modelagem_KNN/KNN_Classificacao_k_47/ df_pred_filmes1_Classificacao_k_47.pickle', compression='gzip')
# Contando valores diferentes de zero em toda a tabela
total_valores_nao_zero = (df_pred_filmes  != 0).sum().sum()
print(f"Total de valores não zero na tabela: {total_valores_nao_zero}")
Total de valores não zero na tabela: 4972042


Quantas vezes cada filme foi recomendado

Para isso vamos olhar os resultados diferentes de zero.

# Contar valores diferentes de zero em cada coluna
count_sem_zero = (df_pred_filmes != 0).sum()

# Calcular a proporção desses valores em relação ao total de linhas
proporcao = count_sem_zero / len(df_pred_filmes)

# Criar um DataFrame de resumo
summary_df = pd.DataFrame({
    'Valores_Diferentes_de_zero': count_sem_zero,
    'Proporcao': proporcao
})


Os 10 filmes mais recomendados
print("Os 10 filmes mais recomendados:")
summary_df.sort_values(by='Valores_Diferentes_de_zero', ascending=False).head(10)
Os 10 filmes mais recomendados:
Valores_Diferentes_de_zero Proporcao
title
Lord of the Rings: The Two Towers, The (2002) 1986 1.0
Princess Bride, The (1987) 1986 1.0
Monty Python and the Holy Grail (1975) 1986 1.0
Monsters, Inc. (2001) 1986 1.0
Dark Knight, The (2008) 1986 1.0
American History X (1998) 1986 1.0
Shining, The (1980) 1986 1.0
Up (2009) 1986 1.0
Toy Story (1995) 1986 1.0
Seven (a.k.a. Se7en) (1995) 1986 1.0


Os 10 filmes menos recomendados
print("Os 10 filmes menos recomendados:")
summary_df.sort_values(by='Valores_Diferentes_de_zero', ascending=True).head(10)
Os 10 filmes menos recomendados:
Valores_Diferentes_de_zero Proporcao
title
Ong-Bak 3: The Final Battle (Ong Bak 3) (2010) 3 0.001511
Rumble in the Air-Conditioned Auditorium: O'Reilly vs. Stewart 2012, The (2012) 3 0.001511
The Hire: Chosen (2001) 3 0.001511
Jack and Diane (2012) 3 0.001511
The Hire: Hostage (2002) 3 0.001511
Lovers of Hate (2010) 3 0.001511
Do-Deca-Pentathlon, The (2012) 3 0.001511
Into the Storm (2009) 3 0.001511
Winning Time: Reggie Miller vs. The New York Knicks (2010) 3 0.001511
Never Goin' Back (2018) 3 0.001511


Listagem de recomendações

Criar um dataframe com 2 colunas: UserId e uma lista com as recomendações.

n_recomendacoes =5

# Função para encontrar os top N filmes recomendados para um usuário
def top_recomendacoes(row, n) -> list:
    ''' Função que encontra as melhores recomendações para cada userId
    Args:
      - row = linha do DataFrame. 
              Cada linha representa as recomendações de filme para cada userId.
      - n = número de filmes a serem recomendados 

    Return:
      - Retorna uma lista com os "n" valores mais altos em cada. 
       Ou seja, retorna com as "n" recomendações de filmes.      
    '''
    # Selecionar e retornar os "n" valores mais altos de cada linha
    return row.nlargest(n).index.tolist()

    #OBS: .index(): Obtemos os nomes dos filmes, ao invés dos valores.
    #     .tolist(): Criamos uma lista com os nomes dos filmes 
# Usando KNeighborRegressor() ----------
# Aplicar a função a cada linha do DataFrame de previsões
recomendacoes = df_pred_filmes.apply(top_recomendacoes, n=n_recomendacoes, axis=1) 

# OBS: apply(... axis=1) -> Aplicar em cada linha
#      n = número de recomendações

# Usando KNeighborsClassifier()-------
# Aplicar a função a cada linha do DataFrame de previsões
#recomendacoes1 = df_pred_filmes1.apply(top_recomendacoes, n=n_recomendacoes, axis=1) 
recomendacoes
userId
128       [Shawshank Redemption, The (1994), Forrest Gum...
172       [Shawshank Redemption, The (1994), Forrest Gum...
465       [Shawshank Redemption, The (1994), Forrest Gum...
598       [Shawshank Redemption, The (1994), Forrest Gum...
919       [Shawshank Redemption, The (1994), Forrest Gum...
                                ...                        
330236    [Shawshank Redemption, The (1994), Lord of the...
330321    [Shawshank Redemption, The (1994), Lord of the...
330496    [Shawshank Redemption, The (1994), Lord of the...
330667    [Shawshank Redemption, The (1994), Lord of the...
330948    [Shawshank Redemption, The (1994), Lord of the...
Length: 1986, dtype: object
# Usando KNeighborRegressor() ----------
# Cria um DataFrame para armazenar as recomendações
df_recomendacoes = pd.DataFrame(recomendacoes.tolist(), index=recomendacoes.index, columns=[f"recomendação_{i+1}" for i in range(n_recomendacoes)])

# Transformar o userId em coluna
df_recomendacoes = df_recomendacoes.reset_index()
df_recomendacoes

# Usando KNeighborsClassifier()-------
# Cria um DataFrame para armazenar as recomendações
#df_recomendacoes1 = pd.DataFrame(recomendacoes1.tolist(), index=recomendacoes1.index, columns=[f"recomendação_{i+1}" for i in range(n_recomendacoes)])

# Transformar o userId em coluna
#df_recomendacoes1 = df_recomendacoes1.reset_index()
userId recomendação_1 recomendação_2 recomendação_3 recomendação_4 recomendação_5
0 128 Shawshank Redemption, The (1994) Forrest Gump (1994) Inception (2010) Lord of the Rings: The Return of the King, The... Fight Club (1999)
1 172 Shawshank Redemption, The (1994) Forrest Gump (1994) Inception (2010) Lord of the Rings: The Return of the King, The... Fight Club (1999)
2 465 Shawshank Redemption, The (1994) Forrest Gump (1994) Inception (2010) Lord of the Rings: The Return of the King, The... Fight Club (1999)
3 598 Shawshank Redemption, The (1994) Forrest Gump (1994) Inception (2010) Lord of the Rings: The Return of the King, The... Fight Club (1999)
4 919 Shawshank Redemption, The (1994) Forrest Gump (1994) Inception (2010) Lord of the Rings: The Return of the King, The... Fight Club (1999)
... ... ... ... ... ... ...
1981 330236 Shawshank Redemption, The (1994) Lord of the Rings: The Two Towers, The (2002) Lord of the Rings: The Return of the King, The... Fight Club (1999) Lord of the Rings: The Fellowship of the Ring,...
1982 330321 Shawshank Redemption, The (1994) Lord of the Rings: The Two Towers, The (2002) Lord of the Rings: The Return of the King, The... Fight Club (1999) Lord of the Rings: The Fellowship of the Ring,...
1983 330496 Shawshank Redemption, The (1994) Lord of the Rings: The Two Towers, The (2002) Lord of the Rings: The Return of the King, The... Fight Club (1999) Lord of the Rings: The Fellowship of the Ring,...
1984 330667 Shawshank Redemption, The (1994) Lord of the Rings: The Two Towers, The (2002) Lord of the Rings: The Return of the King, The... Fight Club (1999) Lord of the Rings: The Fellowship of the Ring,...
1985 330948 Shawshank Redemption, The (1994) Lord of the Rings: The Two Towers, The (2002) Lord of the Rings: The Return of the King, The... Fight Club (1999) Lord of the Rings: The Fellowship of the Ring,...

1986 rows × 6 columns

df_recomendacoes.isna().sum()
userId            0
recomendação_1    0
recomendação_2    0
recomendação_3    0
recomendação_4    0
recomendação_5    0
dtype: int64


Verificar se o userId assistiu a recomendação

Vamos criar uma função chamada assistiu_ou_nao. Esta função vai verificar se os usuários assistiram ou não a alguma das recomendações e em seguida vai retornar 1 (se assistiu), 0 (se não assitiu) e 2 (se o filmes não está no conjunto de teste).

⚠ 📌 A opção de retornar existe, pois na hora do split não foi especificado que os dados de treino e teste deveriam ter os mesmos filmes.
O split focou em pegar todas as avaliações de um usuário. Por exemplo, se o "userId"=1 está no conjunto de treino, todas as avaliações que ele fez também estão no conjunto de treino.
Por isso, o número de filmes nos dados de teste é menor que nos dados de treino.
def assistiu_ou_nao(row, df_teste):
    # Obtém as recomendações para o usuário atual
    recomendacoes = row.drop('userId').values
    
    # Verifica se algum dos filmes recomendados está no df_teste e foi assistido (rating diferente de 0)
    for filme in recomendacoes:
        if filme in df_teste.columns and df_teste.loc[row['userId'], filme] != 0:
            return 1
        elif filme not in df_teste.columns:
            return 2 #Se um filme for recomendado mas não tiver nnos dados de teste
        else:
            return 0 #Se nenhum filme recomendado foi assistido, retorna 0
      
# Usando KNeighborRegressor() ----------
# Aplica a função a cada linha do DataFrame de recomendações para criar a coluna dummy
df_recomendacoes['assistiu_recomendacao'] = df_recomendacoes.apply(assistiu_ou_nao, df_teste=knn_filmes_teste, axis=1)

# Exibe o DataFrame final com a coluna dummy
df_recomendacoes

# Usando KNeighborsClassifier()-------
#df_recomendacoes1['assistiu_recomendacao'] = df_recomendacoes1.apply(assistiu_ou_nao, df_teste=knn_filmes_teste1, axis=1)
#df_recomendacoes1
userId recomendação_1 recomendação_2 recomendação_3 recomendação_4 recomendação_5 assistiu_recomendacao
0 128 Shawshank Redemption, The (1994) Forrest Gump (1994) Inception (2010) Lord of the Rings: The Return of the King, The... Fight Club (1999) 0
1 172 Shawshank Redemption, The (1994) Forrest Gump (1994) Inception (2010) Lord of the Rings: The Return of the King, The... Fight Club (1999) 0
2 465 Shawshank Redemption, The (1994) Forrest Gump (1994) Inception (2010) Lord of the Rings: The Return of the King, The... Fight Club (1999) 0
3 598 Shawshank Redemption, The (1994) Forrest Gump (1994) Inception (2010) Lord of the Rings: The Return of the King, The... Fight Club (1999) 0
4 919 Shawshank Redemption, The (1994) Forrest Gump (1994) Inception (2010) Lord of the Rings: The Return of the King, The... Fight Club (1999) 0
... ... ... ... ... ... ... ...
1981 330236 Shawshank Redemption, The (1994) Lord of the Rings: The Two Towers, The (2002) Lord of the Rings: The Return of the King, The... Fight Club (1999) Lord of the Rings: The Fellowship of the Ring,... 0
1982 330321 Shawshank Redemption, The (1994) Lord of the Rings: The Two Towers, The (2002) Lord of the Rings: The Return of the King, The... Fight Club (1999) Lord of the Rings: The Fellowship of the Ring,... 1
1983 330496 Shawshank Redemption, The (1994) Lord of the Rings: The Two Towers, The (2002) Lord of the Rings: The Return of the King, The... Fight Club (1999) Lord of the Rings: The Fellowship of the Ring,... 0
1984 330667 Shawshank Redemption, The (1994) Lord of the Rings: The Two Towers, The (2002) Lord of the Rings: The Return of the King, The... Fight Club (1999) Lord of the Rings: The Fellowship of the Ring,... 1
1985 330948 Shawshank Redemption, The (1994) Lord of the Rings: The Two Towers, The (2002) Lord of the Rings: The Return of the King, The... Fight Club (1999) Lord of the Rings: The Fellowship of the Ring,... 0

1986 rows × 7 columns

Resultados Modelo 1

Na etapa anterior, geramos as recomendações, e criamos uma coluna (assistiu_recomendacao) para comparar as recomendações com os filmes assisitidos pelos userId dos dados de teste. Cada linha desta coluna é representada por 0, 1 ou 2.

  • 0: significa que nenhum filme da recomendação foi assistido pelo usuário ;
  • 1: significa que pelo menos um dos filmes recomendados foi assistido pelo usuário;
  • 2: significa que o filme recomendado, não está nos dados de teste. A explicação disso foi dada no tópico acima.

Foi testado modelos com diferentes K usando tanto KNeighborRegressor() como KNeighborClassifier(). E, no final, fizemos a contagem dos 0, 1 e 2 da coluna assistiu_recomendacao e comparamos o resultado.

# Usando KNeighborRegressor() ----------
# Resultado quando K=47
#df_recomendacoes['assistiu_recomendacao'].value_counts()

A tabela a seguir tem os resultados dos K usando KNeighborRegressor(). Como é possível observar, o valor K=47 teve o melhor resultado.

A tabela a seguir tem os resultados dos K usando KNeighboClassifier(), como os modelos demoraram muito para rodar, só foram testados dois valores de K.

Modelo 2: Filmes

No modelo 2 vamos tentar uma abordagem diferente. Primeiro vamos selecionar as colunas (filmes) que estão tanto nos dados de treino como de teste. Em seguida, vamos rodar os vizinhos mais próximos não supervisionados (NearestNeighbors) nos dados de treino, e o resultado vamos utilizar para rodar os k vizinhos mais próximos supervisionados (kneighbors) nos dados de teste.

Preparar os dados

Selecionar as colunas

# Garantir que ambos os DataFrames tenham as mesmas colunas (filmes)
common_columns = knn_filmes_treino.columns.intersection(knn_filmes_teste.columns)
knn_filmes_treino = knn_filmes_treino[common_columns]
knn_filmes_teste = knn_filmes_teste[common_columns]

Converter as matrizes em matrizes esparsas

Isso é importante para evitar despérdício de memória.

# Função para converter as matrizes em matrizes esparsas
def create_sparse_matrix(matrix):
    return csr_matrix(matrix)
# Preparar os dados
features_matrix_treino = create_sparse_matrix(knn_filmes_treino.values)
features_matrix_teste = create_sparse_matrix(knn_filmes_teste.values)
features_matrix_treino 
<Compressed Sparse Row sparse matrix of dtype 'float64'
	with 820478 stored elements and shape (7943, 24326)>

Treinar o Nearest Neighbors

Usar o Nearest Neighbors() nos dados de treino. Esta é uma abordagem não surpevisionada.

# Função para criar e treinar o modelo Nearest Neighbors
def train_model(features_matrix, n_neighbors):
    model = NearestNeighbors(n_neighbors=n_neighbors, metric='euclidean', algorithm='ball_tree')
    model.fit(features_matrix)
    return model
# Criar e treinar o modelo
model = train_model(features_matrix_treino, n_neighbors=155)
model
NearestNeighbors(algorithm='ball_tree', metric='euclidean', n_neighbors=155)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

Recomendações

Recomendações para um único userId

# Função para obter recomendações para um usuário específico
def get_recommendations(model, knn_filmes_treino, features_matrix_teste, query_index, n_neighbors):
    distances, indices = model.kneighbors(features_matrix_teste[query_index], n_neighbors=n_neighbors+1)  # +1 para incluir o próprio filme
    recommendations = [knn_filmes_treino.columns[indices.flatten()[i+1]] for i in range(n_neighbors)]  # Excluir o próprio filme (índice 0)
    return recommendations

Recomendações para todos os userId

# Função principal para recomendação de filmes para todos os usuários nos dados de teste
def recommend_movies(knn_filmes_treino, knn_filmes_teste, model, features_matrix_teste, n_neighbors=150):
    # Inicializar uma lista para armazenar as recomendações
    recommendations_list = []

    # Para cada usuário nos dados de teste, encontrar filmes similares usando kneighbors
    for query_index in range(features_matrix_teste.shape[0]):
        recommendations = get_recommendations(model, knn_filmes_treino, features_matrix_teste, query_index, n_neighbors)
        recommendations_list.append({
            'userId': knn_filmes_teste.index[query_index],
            'Recommendation_1': recommendations[0],
            'Recommendation_2': recommendations[1],
            'Recommendation_3': recommendations[2],
            'Recommendation_4': recommendations[3],
            'Recommendation_5': recommendations[4]
        })

    # Converter a lista de recomendações em DataFrame
    recommendations_df = pd.DataFrame(recommendations_list)
    return recommendations_df
# Garantir que os índices são 'userId' e obter recomendações
recommendations = recommend_movies(knn_filmes_treino, knn_filmes_teste, model, features_matrix_teste, n_neighbors=150)
recommendations
userId Recommendation_1 Recommendation_2 Recommendation_3 Recommendation_4 Recommendation_5
0 128 Martian Child (2007) Death Note 2: The Last Name (2006) Beyond the Lights (2014) Jacknife (1989) Arthur (2011)
1 172 Jacob's Ladder (1990) Krampus (2015) Asterix vs. Caesar (Astérix et la surprise de ... Burning, The (1981) Killer Elite (2011)
2 465 G-Force (2009) Batman: The Killing Joke (2016) Conjuring, The (2013) Mulan (2009) God Grew Tired of Us (2006)
3 598 Henry: Portrait of a Serial Killer (1986) Crow: Salvation, The (2000) Kika (1993) Belladonna of Sadness (1973) Da 5 Bloods (2020)
4 919 Dhoom (2004) Jimmy Carr: Stand Up (2005) I Am Sam (2001) Monos (2019) Garfield: The Movie (2004)
... ... ... ... ... ... ...
1981 330236 Death Note 2: The Last Name (2006) Jacknife (1989) Arthur (2011) Beyond the Lights (2014) Ex Machina (2015)
1982 330321 Enough Said (2013) Lost City, The (2005) In Country (1989) Descendants (2015) Ice Age: The Great Egg-Scapade (2016)
1983 330496 Harmontown (2014) Lady in a Cage (1964) Love & Friendship (2016) End of Watch (2012) Kill Bill: Vol. 2 (2004)
1984 330667 End of Days (1999) Happiest Season (2020) Farinelli: il castrato (1994) Emperor's New Clothes, The (2001) Hotel Mumbai (2019)
1985 330948 Anastasia (1956) Aloha (2015) Friday the 13th Part VII: The New Blood (1988) Little Children (2006) Grand Day Out with Wallace and Gromit, A (1989)

1986 rows × 6 columns

Criar coluna assitiu_recomendacao

Esta é uma coluna dummy em que:

  • 1 significa que o usuário assitiu pelo menos 1 recomendação;
  • 0 significa que o usuário não assitiu a nenhuma recomendação.
def assistiu_ou_nao(row, df_teste):
    # Obtém as recomendações para o usuário atual
    recomendacoes = row.drop('userId').values
    
    # Verifica se algum dos filmes recomendados está no df_teste e foi assistido (rating diferente de 0)
    for filme in recomendacoes:
        if filme in df_teste.columns and df_teste.loc[row['userId'], filme] != 0:
            return 1
    return 0 #Se nenhum filme recomendado foi assistido, retorna 0
# Aplica a função a cada linha do DataFrame de recomendações para criar a coluna dummy
recommendations['assistiu_recomendacao'] = recommendations.apply(assistiu_ou_nao, df_teste=knn_filmes_teste, axis=1)

# Exibe o DataFrame final com a coluna dummy
recommendations
userId Recommendation_1 Recommendation_2 Recommendation_3 Recommendation_4 Recommendation_5 assistiu_recomendacao
0 128 Martian Child (2007) Death Note 2: The Last Name (2006) Beyond the Lights (2014) Jacknife (1989) Arthur (2011) 0
1 172 Jacob's Ladder (1990) Krampus (2015) Asterix vs. Caesar (Astérix et la surprise de ... Burning, The (1981) Killer Elite (2011) 0
2 465 G-Force (2009) Batman: The Killing Joke (2016) Conjuring, The (2013) Mulan (2009) God Grew Tired of Us (2006) 0
3 598 Henry: Portrait of a Serial Killer (1986) Crow: Salvation, The (2000) Kika (1993) Belladonna of Sadness (1973) Da 5 Bloods (2020) 0
4 919 Dhoom (2004) Jimmy Carr: Stand Up (2005) I Am Sam (2001) Monos (2019) Garfield: The Movie (2004) 0
... ... ... ... ... ... ... ...
1981 330236 Death Note 2: The Last Name (2006) Jacknife (1989) Arthur (2011) Beyond the Lights (2014) Ex Machina (2015) 0
1982 330321 Enough Said (2013) Lost City, The (2005) In Country (1989) Descendants (2015) Ice Age: The Great Egg-Scapade (2016) 0
1983 330496 Harmontown (2014) Lady in a Cage (1964) Love & Friendship (2016) End of Watch (2012) Kill Bill: Vol. 2 (2004) 0
1984 330667 End of Days (1999) Happiest Season (2020) Farinelli: il castrato (1994) Emperor's New Clothes, The (2001) Hotel Mumbai (2019) 0
1985 330948 Anastasia (1956) Aloha (2015) Friday the 13th Part VII: The New Blood (1988) Little Children (2006) Grand Day Out with Wallace and Gromit, A (1989) 1

1986 rows × 7 columns

# n_neighbors=150
recommendations['assistiu_recomendacao'].value_counts()
assistiu_recomendacao
0    1903
1      83
Name: count, dtype: int64

Referências Bibliográficas

FONTANA, Éliton. Introdução aos Algoritmos de Aprendizagem Supervisionada. 1ª Edição. Paraná, 2020. Disponível em: https://fontana.paginas.ufsc.br/files/2018/03/apostila_ML_pt2.pdf

LANTZ, B. Machine Learning with R: Expert techniques for predictive modeling. Birmingham, England: Packt Publishing, 2019.



Ícone Ver README do projeto